package com.gridpower.spreadsheet.client.models;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.gridpower.spreadsheet.client.RemoteServiceSequencer;
import com.gridpower.spreadsheet.client.RemoteServiceSequencer.RemoteCall;
import com.gridpower.spreadsheet.client.FileService.Util;

public class AppModel {
	static public class CellSelection {
		public int rowStart;
		public int colStart;
		public int rowEnd;
		public int colEnd;
		public void normalize() {
			  if (rowEnd < rowStart) {
				  int temp = rowStart;
				  rowStart = rowEnd;
				  rowEnd = temp;
			  }
			  if (colEnd < colStart) {
				  int temp = colStart;
				  colStart = colEnd;
				  colEnd = temp;
			  }
		}
	}
	static public class DocInfo implements IsSerializable {// serialiable
		public int rowCount;
		public int colCount;
		public int fixedRowCount;
		public int fixedColCount;
	}
	static public class RemoteFileInfo implements IsSerializable {// serialiable
		public int fileId = -1;
		public int currentDocId = -1;
		public int docNum = 0;
		/**
		 * @gwt.typeArgs <java.lang.Integer>
		 */
		public ArrayList docIds = new ArrayList();
		/**
		 * @gwt.typeArgs <java.lang.String>
		 */
		public ArrayList docNames = new ArrayList();
		/**
		 * @gwt.typeArgs <DocInfo>
		 */
		public ArrayList docInfos = new ArrayList();// not suitable
		
		public int docId2Idx(int docId) {
			int ret = -1;
			Iterator itr = docIds.iterator();
			int idx = 0;
			while (itr.hasNext()) {
				if ((Integer)itr.next() == docId) {
					ret = idx;
					break;
				}
				idx++;
			}
			return ret;
		}
	}
	/**
	 * the singleton for the application 
	 */
	protected static AppModel appModel = new AppModel();
	
	/**
	 * UI action status
	 */
	// selection data, deprecated
	protected CellSelection selection;
	boolean cellSelected = false;

	/**
	 * the mouse model and the selection model
	 */
	UIMouseModel mouseModel = new UIMouseModel();
	UISelectModel selectModel = new UISelectModel();
	/**
	 *  current file infos
	 */
	RemoteFileInfo fileInfo = null;
	final protected HashMap docId2Docs = new HashMap();
	DocModel currentDocModel = null;
	protected int uid = -1;
	/**
	 * doc loading helper
	 */
	protected HashMap docLoadingStatus = new HashMap();
	

	
	/**
	 * timer and the listeners
	 */
	public static interface TimerListener {
		public void run();
	}
	protected final ArrayList timerListeners = new ArrayList();
	
	private Timer timer = new Timer () {
		public void run() {
			GWT.log("timer", null);
			Iterator itr = timerListeners.iterator();
			while (itr.hasNext()) {
				((TimerListener)itr.next()).run();
			}
		}
	};
	// DocModelChange Listeners
	/**
	 * the observer will run this function if docId = current Id
	 * this will keep the asyschonous calling logical
	 */
	public static abstract class DocModelChangeListener {
		public void runInternal(int docId) {
			
		}
		public void onChange(int docId) {
			if (AppModel.getInstance().getCurrentDocId() == docId) {
				runInternal(docId);
			}
		}
	}
	final ArrayList docListeners = new ArrayList();
	
	
	
	/**
	 * default constructor, start a app range timer.
	 */
	public  AppModel() {
		// schedule a global level timer
		timer.scheduleRepeating(333);
		// the default DocModelChangeListener
		addDocChangeListener(new DocModelChangeListener() {//set current model
			public void runInternal(int docId) {
				setCurrentDocModel(docId);
			}
		});
	}
	static public AppModel getInstance() {
		return appModel;
	}
	////////////////////
	// Loading file or doc model
	/////////////////
	/**
	 * When file info successfully download, call this function
	 * Although there is a kind of sequencer, but when two rpcs are intrin
	 * @param fi
	 */
	public void onLoadFileRes(RemoteFileInfo fi) {
		fileInfo = fi;
		// load the current doc
		if (fileInfo != null) {
			// load the current file
			loadCurrentDocModel();
		}
	}
	/**
	 * When doc cell successfully download, call this function
	 * @param fileName
	 */
	public void onLoadDocCells(int docId, GridCell[] cells) {
		// create new DocCells, and put it in to a new DocModel
		DocModel docModel = new DocModel();
		// put the cells to the docModel
		docModel.initialize(cells);
		// set current Model
		docId2Docs.put(docId, docModel);
		DocInfo di = (DocInfo)fileInfo.docInfos.get(fileInfo.currentDocId);
		docModel.setFixedColCount(di.fixedColCount);
		docModel.setFixedColCount(di.fixedRowCount);
		docModel.setColCount(di.colCount);
		docModel.setRowCount(di.rowCount);
		// unlock the lock
		Boolean isLoading = (Boolean)docLoadingStatus.get(docId);
		if (isLoading != null) {
			isLoading = false;
		} else {
			// exception happens
		}
		// fire the event, OK, now notify the listeners!
		fireDocModelChange(docId);
	}
	/**
	 * get File Info and get DocModel content
	 */
	public void loadFile(String fileName) {
		// remote get file infomation.
		final String fn = fileName;
		RemoteServiceSequencer.getInstance().call(new RemoteCall() {
			public void run() {
				Util.getInstance().getFileInfo(1, fn, new AsyncCallback() {
			          public void onFailure(Throwable caught) {
			            // super.run();
			          }
			          public void onSuccess(Object result) {
			        	  // do something
			        	  RemoteFileInfo fi = (RemoteFileInfo) result;
			        	  onLoadFileRes(fi);// a solution
			        	  GWT.log(fi.docNames.toString(), null);// just a test
			        	  RemoteServiceSequencer.getInstance().releaseLock();
			          }
				});
			}
		}
		);
		// remote get current document.
	}
	public void setCurrentDocModel(int docId) {
		this.fileInfo.currentDocId = docId;
		loadCurrentDocModel();
	}

	public void loadCurrentDocModel() {
		// DocModel ret = null;
		// get the cells of a DocModel;
		// put it into the DocModel
		loadDocModelById(fileInfo.currentDocId);
	}
	/**
	 * todo: the docId can be reload from the server
	 * @param docId
	 */
	public void loadDocModelById(int docId) {
		DocModel ret = (DocModel)docId2Docs.get(docId);
		final int id = docId;
		// get the content from remote or directly load docModel
		if ( ret != null) {
			this.currentDocModel = ret;
			// todo: notify the listeners
		} else {
			Boolean isLoading = (Boolean)docLoadingStatus.get(id);
			if (isLoading == null) {
				isLoading = new Boolean(false);
				docLoadingStatus.put(id, isLoading);
			}
			if ( isLoading == false) {
				isLoading = true;
				RemoteServiceSequencer.getInstance().call(new RemoteCall() {
					public void run() {
						Util.getInstance().getAllCellsForDoc(id, new AsyncCallback() {
							public void onFailure(Throwable caught) {
								GWT.log("ff", null);
							}
							public void onSuccess(Object result) {
								// do something
								GridCell[] cells = (GridCell[]) result;
								onLoadDocCells(id, cells);
								RemoteServiceSequencer.getInstance().releaseLock();
							}
						});
					}
				}
				);
			}
		}
	}

	
	/**
	 * get whole document content into Gridcell List.
	 */
	protected ArrayList getDocContentById(int docId) {
		return null;
	}

    ///////////////////
	// Model Changing
	///////////////////
	/**
	 * Listeners and Observer firer
	 */
	public void fireDocModelChange(int docId) {
		for (int i = 0; i < docListeners.size(); ++i)
			((DocModelChangeListener)docListeners.get(i)).onChange(docId);
	}
	public void addTimerListener (TimerListener listener) {
		timerListeners.add(listener);
	}
	public void addDocChangeListener (DocModelChangeListener listener) {
		docListeners.add(listener);
	}
	public void setSelection(int rowStart, int colStart, int rowEnd, int colEnd) {
		selection.rowStart = rowStart;
		selection.rowEnd = rowEnd;
		selection.colStart = colStart;
		selection.colEnd = colEnd;
		cellSelected = true;// noneed
	}
	////////////////////
	// getters and setters
	/////////////////
	public int getCurrentDocId() {
		return fileInfo.currentDocId;
	}
	public DocModel getCurrentDoc() {
		return currentDocModel;
	}
	
	public int getUid() {
		return uid;
	}
	public RemoteFileInfo getFileInfo() {
		return fileInfo;
	}
	public UIMouseModel getMouseModel() {
		return mouseModel;
	}
	public UISelectModel getSelectModel() {
		return selectModel;
	}
	
	
	public void setUid(int uid) {
		this.uid = uid;
	}
	public void setFileInfo(RemoteFileInfo fileInfo) {
		this.fileInfo = fileInfo;
	}

	public void setMouseModel(UIMouseModel mouseModel) {
		this.mouseModel = mouseModel;
	}
	public void setSelectModel(UISelectModel selectModel) {
		this.selectModel = selectModel;
	}
}
